package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/astaxie/beego/orm"
	_ "github.com/go-sql-driver/mysql"
	"github.com/spf13/viper"
)

const COVER_EXIST_FILES = true

type TableColumnInfo struct {
	Column_name              string
	Data_type                string
	Character_maximum_length int64
	Is_nullable              string
	Column_defaut            int64
	Extra                    string
	Pk                       bool
}

func gen_model_struct_field(tableColumnInfo TableColumnInfo) (string, bool) {
	line := "\t" + tableColumnInfo.Column_name
	have_time_field := false

	if strings.Contains(tableColumnInfo.Data_type, "char") ||
		strings.Contains(tableColumnInfo.Data_type, "text") ||
		strings.Contains(tableColumnInfo.Data_type, "blob") ||
		strings.Contains(tableColumnInfo.Data_type, "binary") ||
		strings.Contains(tableColumnInfo.Data_type, "enum") ||
		strings.Contains(tableColumnInfo.Data_type, "set") {
		line += "\tstring"
	} else if strings.Contains(tableColumnInfo.Data_type, "int") &&
		tableColumnInfo.Data_type != "bigint" {
		line += "\tint"
	} else if tableColumnInfo.Data_type == "bigint" {
		line += "\tint64"
	} else if strings.Contains(tableColumnInfo.Data_type, "date") ||
		strings.Contains(tableColumnInfo.Data_type, "time") ||
		strings.Contains(tableColumnInfo.Data_type, "datetime") ||
		strings.Contains(tableColumnInfo.Data_type, "timestamp") ||
		strings.Contains(tableColumnInfo.Data_type, "year") {
		line += "\ttime.Time"
		have_time_field = true
	}

	return line, have_time_field
}

func gen_file(dir string, filename string, content string) {
	err := os.MkdirAll(filepath.Dir(dir), 0755) // 注意：需要引入 "path/filepath" 包
	if err != nil {
		fmt.Println("创建目录失败:", err)
		return
	}

	pathname := dir + filename

	fd, err := os.Create(pathname)
	if err != nil {
		panic(err)
	}

	_, err = fd.Write([]byte(content))
	if err != nil {
		panic(err)
	}

	fd.Close()
}

func gen_model_file(tableName string, tableColumnInfos []TableColumnInfo) {
	lines := ""
	import_content := ""
	for i := range tableColumnInfos {
		line, have_time_field := gen_model_struct_field(tableColumnInfos[i])
		if have_time_field {
			import_content = "import \"time\"\n"
		}

		lines += "\n" + line
	}
	tableName_lower := strings.ToLower(tableName)
	tableName_capitcal := capitcalize_string(tableName)

	file_content := strings.ReplaceAll(model_template, "{%import_content%}", import_content)
	file_content = strings.ReplaceAll(file_content, "{%table_name_capital%}", tableName_capitcal)
	file_content = strings.ReplaceAll(file_content, "{%table_name_lower%}", tableName_lower)
	file_content = strings.ReplaceAll(file_content, "{%struct_lines%}", lines)

	filename := tableName_lower + ".go"
	dir := "models/"
	gen_file(dir, filename, file_content)
}

func gen_service_struct_field(tableName string, tableColumnInfo TableColumnInfo) (string, bool) {
	tableName_lower := strings.ToLower(tableName)
	cond_line_up := "\tif params.Has(\"" + strings.ToLower(tableColumnInfo.Column_name) + "\") {\n"
	col_join := "\t\tcols = append(cols, \"" + strings.ToLower(tableColumnInfo.Column_name) + "\")\n"
	cond_line_bottom := "\t}"

	right_value := "params.Get(\"" + strings.ToLower(tableColumnInfo.Column_name) + "\")"
	line := "\t" + tableName_lower + "." + tableColumnInfo.Column_name + " = " + right_value + "\n"
	have_time_field := false

	if strings.Contains(tableColumnInfo.Data_type, "int") {
		right_value = "strconv.Atoi(" + right_value + ")"
		line = "\t" + tableName_lower + "." + tableColumnInfo.Column_name + ", _ = " + right_value + "\n"
	} else if strings.Contains(tableColumnInfo.Data_type, "date") ||
		strings.Contains(tableColumnInfo.Data_type, "time") ||
		strings.Contains(tableColumnInfo.Data_type, "datetime") ||
		strings.Contains(tableColumnInfo.Data_type, "timestamp") ||
		strings.Contains(tableColumnInfo.Data_type, "year") {

		right_value = "time.Parse(\"2006-01-02 15:04:05\", " + right_value + ")"
		line = "\t" + tableName_lower + "." + tableColumnInfo.Column_name + ", _ = " + right_value + "\n"
		have_time_field = true
	}

	if tableColumnInfo.Pk {
		return line, have_time_field
	}

	return cond_line_up + col_join + "\t" + line + cond_line_bottom, have_time_field
}

func gen_service_file(tableName string, tableColumnInfos []TableColumnInfo) {
	common_lines := ""
	pk_line := ""
	import_time_content := ""
	pk_name := ""
	for i := range tableColumnInfos {
		line, have_time_field := gen_service_struct_field(tableName, tableColumnInfos[i])
		if tableColumnInfos[i].Pk {
			pk_line = line
			pk_name = strings.ToLower(tableColumnInfos[i].Column_name)
		} else {
			common_lines += "\n" + line
		}
		if have_time_field {
			import_time_content = "\n\t\"time\""
		}
	}
	tableName_lower := strings.ToLower(tableName)
	tableName_Captical := strings.ToUpper(tableName_lower[:1]) + tableName_lower[1:]

	file_content := strings.ReplaceAll(service_template, "{%import_time_content%}", import_time_content)
	file_content = strings.ReplaceAll(file_content, "{%table_name_capital%}", tableName_Captical)
	file_content = strings.ReplaceAll(file_content, "{%table_name_lower%}", tableName_lower)
	file_content = strings.ReplaceAll(file_content, "{%common_lines%}", common_lines)
	file_content = strings.ReplaceAll(file_content, "{%pk_line%}", pk_line)
	file_content = strings.ReplaceAll(file_content, "{%pk_name%}", pk_name)

	filename := tableName_lower + ".go"
	dir := "services/"
	gen_file(dir, filename, file_content)
}

func gen_controller_file(tableName string) {
	tableName_lower := strings.ToLower(tableName)
	tableName_Captical := capitcalize_string(tableName)

	file_content := strings.ReplaceAll(controller_template, "{%table_name_capital%}", tableName_Captical)

	filename := tableName_lower + ".go"
	dir := "controllers/"
	gen_file(dir, filename, file_content)
}

func gen_router_file(tableName string) {
	tableName_lower := strings.ToLower(tableName)
	tableName_Captical := capitcalize_string(tableName)

	file_content := strings.ReplaceAll(router_template, "{%table_name_capital%}", tableName_Captical)
	file_content = strings.ReplaceAll(file_content, "{%table_name_lower%}", tableName_lower)

	filename := tableName_lower + ".go"
	dir := "routers/"
	gen_file(dir, filename, file_content)
}

func gen_table_fields(dbname string, tables []string) {
	orm1 := orm.NewOrm()
	for i := range tables {
		table := tables[i]
		var fields []orm.Params
		sql_template := `SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, COLUMN_DEFAULT,EXTRA
			FROM information_schema.COLUMNS
			WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'`
		sql := fmt.Sprintf(sql_template, dbname, table)
		_, err := orm1.Raw(sql).Values(&fields)
		if err != nil {
			panic(err)
		}

		pri_sql_tpl := `SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
							WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s' AND COLUMN_KEY='PRI';`
		sql = fmt.Sprintf(pri_sql_tpl, dbname, table)
		var pri_cols []orm.Params
		_, err = orm1.Raw(sql).Values(&pri_cols)
		pk_name, _ := pri_cols[0]["COLUMN_NAME"].(string)
		if err != nil {
			panic(err)
		}

		tableColumnInfos := []TableColumnInfo{}
		for i := range fields {
			Column_name, _ := fields[i]["COLUMN_NAME"].(string)
			Pk := strings.EqualFold(Column_name, pk_name)
			Data_type, _ := fields[i]["DATA_TYPE"].(string)
			Character_maximum_length, _ := fields[i]["CHARACTER_MAXIMUM_LENGTH"].(int64)
			Is_nullable, _ := fields[i]["IS_NULLABLE"].(string)
			Column_default, _ := fields[i]["COLUMN_DEFAULT"].(int64)
			Extra, _ := fields[i]["EXTRA"].(string)
			tableColumnInfo := TableColumnInfo{
				capitcalize_string(Column_name),
				Data_type,
				Character_maximum_length,
				Is_nullable,
				Column_default,
				Extra,
				Pk,
			}
			tableColumnInfos = append(tableColumnInfos, tableColumnInfo)
		}

		gen_model_file(table, tableColumnInfos)
		gen_service_file(table, tableColumnInfos)
		gen_controller_file(table)
		gen_router_file(table)
	}

}

func check_files_exsit(tables []string) bool {
	dirs := []string{"controllers/", "models/", "services/", "routers/"}
	exist_files := []string{}
	for i := range dirs {
		for j := range tables {
			pathname := dirs[i] + strings.ToLower(tables[j]) + ".go"
			_, err := os.Stat(pathname)
			if !os.IsNotExist(err) {
				exist_files = append(exist_files, pathname)
			}
		}
	}

	if len(exist_files) > 0 {
		println("待生成MVC文件已有存在, 请先移除: ")
		for i := range exist_files {
			println("\t" + exist_files[i])
		}
		return true
	}

	return false
}

func connect_db_and_show_tables() (string, []string) {
	viper.SetConfigName("table") // 配置文件名称（不包含扩展名）
	viper.SetConfigType("toml")  // 配置文件格式
	viper.AddConfigPath(".")     // 查找配置文件所在的路径

	viper.SetDefault("database.dbname", "default")
	viper.SetDefault("database.user", "root")
	viper.SetDefault("database.password", "root")
	viper.SetDefault("database.host", "localhost")
	viper.SetDefault("database.port", "3306")

	if err := viper.ReadInConfig(); err != nil {
		fmt.Printf("Error reading table config file, %s", err)
	}

	dbhost, _ := viper.Get("database.host").(string)
	dbport, _ := viper.Get("database.port").(string)
	dbuser, _ := viper.Get("database.user").(string)
	dbpassword, _ := viper.Get("database.password").(string)
	dbname, _ := viper.Get("database.dbname").(string)

	dsn := dbuser + ":" + dbpassword + "@tcp(" + dbhost + ":" + dbport + ")/" + dbname
	orm.RegisterDataBase("default", "mysql", dsn)

	tables_line, _ := viper.Get("create.tables").(string)
	tables := strings.Split(tables_line, ",")

	return dbname, tables
}

func main() {
	dbname, tables := connect_db_and_show_tables()
	if len(tables) < 1 {
		println("Done.Empty table list.")
		return
	}

	if !COVER_EXIST_FILES && check_files_exsit(tables) {
		return
	}

	gen_table_fields(dbname, tables)
	println("Done.Success.")
}

func capitcalize_string(s string) string {
	lw := strings.ToLower(s)
	return strings.ToUpper(lw[:1]) + lw[1:]
}

var model_template = `
package models

{%import_content%}
type {%table_name_capital%} struct {{%struct_lines%}
}

func (m *{%table_name_capital%}) TableName() string {
	return "{%table_name_lower%}"
}
	
`

var service_template = `
package services

import (
	"beego_blog/models"
	"net/url"
	"strconv"
	"log"{%import_time_content%}
)

type {%table_name_capital%}Service struct {
	baseService
}

func (c *{%table_name_capital%}Service) Add(params url.Values) bool {
	{%table_name_lower%} := models.{%table_name_capital%}{}
	cols := []string{}
{%common_lines%}

	if _, err := c.o.Insert(&{%table_name_lower%}); err != nil {
		log.Println("Add err", err)
		return false
	} else {
		return true
	}
}

func (c *{%table_name_capital%}Service) Delete(params url.Values) bool {
	{%table_name_lower%} := models.{%table_name_capital%}{}
{%pk_line%}
	if _, err := c.o.Delete(&{%table_name_lower%}); err != nil {
		log.Println("Delete err", err)
		return false
	} else {
		return true
	}
}

func (c *{%table_name_capital%}Service) Query(params url.Values) []*models.{%table_name_capital%} {
	{%table_name_lower%}s := []*models.{%table_name_capital%}{}
	{%table_name_lower%} := models.{%table_name_capital%}{}
	cols := []string{}
{%common_lines%}

	Query_by_model(c.o, "{%pk_name%}", cols, {%table_name_lower%}, &{%table_name_lower%}s)
	return {%table_name_lower%}s
}

func (c *{%table_name_capital%}Service) QueryByPk(params url.Values) models.{%table_name_capital%} {
	{%table_name_lower%} := models.{%table_name_capital%}{}
{%pk_line%}
	c.o.Read(&{%table_name_lower%})
	return {%table_name_lower%}
}

func (c *{%table_name_capital%}Service) Update(params url.Values) bool {
	{%table_name_lower%} := models.{%table_name_capital%}{}
	cols := []string{}
{%pk_line%}{%common_lines%}

	if _, err := c.o.Update(&{%table_name_lower%}, cols...); err != nil {
		log.Println("Update err", err)
		return false
	} else {
		return true
	}
}
	
`

var controller_template = `
package controllers

import (
	"beego_blog/services"
)

type {%table_name_capital%}Controller struct {
	baseController
	service services.{%table_name_capital%}Service
}

func (c *{%table_name_capital%}Controller) Add() {
	if c.Ctx.Request.Method == "POST" {
		c.SuccessJson(c.service.Add(c.Input()))
	}
}

func (c *{%table_name_capital%}Controller) Delete() {
	if c.Ctx.Request.Method == "POST" {
		c.SuccessJson(c.service.Delete(c.Input()))
	}
}

func (c *{%table_name_capital%}Controller) Query() {
	if c.Ctx.Request.Method == "POST" {
		c.SuccessJson(c.service.Query(c.Input()))
	}
}

func (c *{%table_name_capital%}Controller) QueryByPk() {
	if c.Ctx.Request.Method == "POST" {
		c.SuccessJson(c.service.QueryByPk(c.Input()))
	}
}

func (c *{%table_name_capital%}Controller) Update() {
	if c.Ctx.Request.Method == "POST" {
		c.SuccessJson(c.service.Update(c.Input()))
	}
}

func (c *{%table_name_capital%}Controller) Prepare() {
	c.baseController.Prepare()
	c.service = services.{%table_name_capital%}Service{}
	c.service.Prepare()
}
	
`

var router_template = `
package routers

import (
	"beego_blog/controllers"
	"github.com/astaxie/beego"
)

func {%table_name_capital%}_init() {
	beego.Router("/{%table_name_lower%}/add", &controllers.{%table_name_capital%}Controller{}, "post:Add")
	beego.Router("/{%table_name_lower%}/delete", &controllers.{%table_name_capital%}Controller{}, "post:Delete")
	beego.Router("/{%table_name_lower%}/query", &controllers.{%table_name_capital%}Controller{}, "post:Query")
	beego.Router("/{%table_name_lower%}/queryByPk", &controllers.{%table_name_capital%}Controller{}, "post:QueryByPk")
	beego.Router("/{%table_name_lower%}/update", &controllers.{%table_name_capital%}Controller{}, "post:Update")
}

`
